/*
 * Copyright (c) 2009-2011, Petr Panteleyev <petr@panteleyev.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *    3. The name of the author may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.panteleyev.blogclient;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.EventObject;

import javax.swing.AbstractAction;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;

import org.panteleyev.blogapi.IBlogEntry;
import org.panteleyev.blogclient.events.OutboxChangedEvent;
import org.panteleyev.swing.BaseTable;
import org.panteleyev.swing.SwingWorkerEx;
import org.panteleyev.swing.TableUtil;
import org.panteleyev.swing.events.UpdateFrameEvent;
import org.panteleyev.utilities.MD5;
import org.panteleyev.utilities.Updatable;
import org.panteleyev.utilities.events.AppEventListener;

/**
 *
 * @author Petr Panteleyev <petr@panteleyev.org>
 */
public abstract class BloggerOutboxFrame<T extends UserProfile<E>,E extends IBlogEntry> extends BloggerFrame<T> implements Updatable {
    protected static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd kk:mm");
    protected static final String HEADER_DATE = L10N.OUTBOX_FRAME_TABLE_HEADER_DATE.s();
    protected static final String HEADER_SUBJECT = L10N.OUTBOX_FRAME_TABLE_HEADER_SUBJECT.s();

    public static String getOutboxFileName() {
        return MD5.encode(Long.toString(System.currentTimeMillis())) + ".pb";
    }

    protected abstract class OutboxTableModel extends AbstractTableModel {
        @Override
        public int getRowCount() {
            return getOutbox().size();
        }

        @Override
        public Object getValueAt(int row, int col) {
            IBlogEntry event = getOutbox().get(row);
            switch (col) {
                case 0:
                    Date date = event.getDate();
                    return (date == null)? null : dateFormat.format(event.getDate());
                case 1:
                    return event.getBlogName();
                case 2:
                    return event.getSubject();
                default:
                    return null;
            }
        }
    }

    private class OutboxThread extends SwingWorkerEx<Object,Object> {
        protected int[] rows;
        private ArrayList<BlogPostEditorFrame<?,?>> windowsToClose = new ArrayList<>();

        OutboxThread(int[] rows) {
            super(BloggerOutboxFrame.this);
            this.rows = rows;
        }

        @Override
        protected Object doInBackground() throws Exception {
            for (int i : rows) {
                E event = getOutbox().get(i);
                File file = getFiles().get(i);
                try {
                    getProfile().publishEntry(event);
                    file.delete();

                    /* Close corresponding window if opened */
                    BlogPostEditorFrame<?,?> w = getProfile().findBlogEntryEditor(event);
                    if (w != null) {
                        windowsToClose.add(w);
                    }
                } catch (Exception e) {
                    setException(e);
                }
            }

            return null;
        }

        @Override
        protected void done() {
            if (getException() == null) {
                for (BlogPostEditorFrame w : windowsToClose) {
                    w.afterPostSent();
                }

                receive(new UpdateFrameEvent(BloggerOutboxFrame.this));
            } else {
                handleException(getException());
            }

            super.done();
        }
    }

    private AbstractAction  editEntryAction;
    private AbstractAction  deleteEntryAction;
    private AbstractAction  publishEntryAction;

    private BaseTable       table = new BaseTable();
    private JScrollPane     scroller = new JScrollPane(table);
    private AbstractTableModel tableModel;

    private ArrayList<File> files = new ArrayList<>();
    private ArrayList<E> outbox = new ArrayList<>();

    public BloggerOutboxFrame(T profile) {
        super(profile);
        this.setIconImage(Icon.OUTBOX.I().getImage());

        createActions();
        publishEntryAction.setEnabled(getProfile().isOnline());
        createMenuBar();
        initComponents();
        table.setModel(tableModel = createTableModel());

        addAppEventListener(OutboxChangedEvent.class, new AppEventListener() {
            @Override
            public void eventReceived(EventObject event) {
                update();
            }
        });

        receive(new UpdateFrameEvent(this));
    }

    private void initComponents() {
        setTitle("Outbox");

        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent evt) {
                if (evt.getClickCount() == 2) {
                    onEditEntry();
                }
            }
        });
        scroller.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent evt) {
                TableUtil.adjustTableColumnSizes(getTable(), 2, scroller);
            }
        });
        getContentPane().setLayout(new BorderLayout());
        getContentPane().add(scroller, BorderLayout.CENTER);
        Options.getBounds(this);
    }

    private void createActions() {
        publishEntryAction = new AbstractAction(L10N.OUTBOX_FRAME_PUBLISH_ACTION.s(), Icon.EMPTY.i()) {
            @Override
            public void actionPerformed(ActionEvent e) {
                onPublishEntry();
            }
        };

        editEntryAction = new AbstractAction(L10N.OUTBOX_FRAME_EDIT_ACTION.s(), Icon.EMPTY.i()) {
            @Override
            public void actionPerformed(ActionEvent e) {
                onEditEntry();
            }
        };

        deleteEntryAction = new AbstractAction(L10N.OUTBOX_FRAME_DELETE_ACTION.s(), Icon.EMPTY.i()) {
            @Override
            public void actionPerformed(ActionEvent e) {
                onDeleteEntry();
            }
        };
    }

    private void createMenuBar() {
        createMenuBar(
            createFileMenu(),
            createMenu("Entry", null,
                getPublishEntryAction(),
                null,
                getEditEntryAction(),
                getDeleteEntryAction()
            ),
            createProfileMenu(),
            createToolsMenu(),
            createWindowMenu(),
            createHelpMenu()
        );

        getScroller().setComponentPopupMenu(
            createMenu("Entry", null,
                getPublishEntryAction(),
                null,
                getEditEntryAction(),
                getDeleteEntryAction()
            ).getPopupMenu()
        );
    }

    public AbstractAction getDeleteEntryAction() {
        return deleteEntryAction;
    }

    public AbstractAction getEditEntryAction() {
        return editEntryAction;
    }

    public AbstractAction getPublishEntryAction() {
        return publishEntryAction;
    }

    public JScrollPane getScroller() {
        return scroller;
    }

    public JTable getTable() {
        return table;
    }

    public ArrayList<File> getFiles() {
        return files;
    }

    public ArrayList<E> getOutbox() {
        return outbox;
    }

    public AbstractTableModel getTableModel() {
        return tableModel;
    }

    @Override
    public void update() {
        outbox.clear();
        files.clear();

        File outboxDir = getProfile().getOutboxDirectory();

        File[] dirList = outboxDir.listFiles(new PetrusBloggerFileFilter());

        outbox.ensureCapacity(dirList.length);
        files.ensureCapacity(dirList.length);

        for (File file : dirList) {
            try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
                E event = (E)in.readObject();
                getProfile().updateEntryAdterRestore(event);
                outbox.add(event);
                files.add(file);
            } catch (IOException | ClassNotFoundException e) {
                MainClass.handleException(this, e);
            }
        }

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                tableModel.fireTableDataChanged();
                TableUtil.adjustTableColumnSizes(getTable(), 2, scroller);
            }
        });
    }

    private void onEditEntry() {
        int index = getTable().getSelectedRow();
        if (index != -1) {
            E event = getOutbox().get(index);
            File file = getFiles().get(index);
            getProfile().openBlogEntryEditor(event, file);
        }
    }

    private void onDeleteEntry() {
        int[] rows = getTable().getSelectedRows();

        if (rows.length > 0 && MainClass.showConfirmDialog(this)) {
            for (int i : rows) {
                E event = outbox.get(i);
                File file = getFiles().get(i);

                if (event != null && file != null) {
                    /* Delete file from outbox folder */
                    file.delete();

                    /* Close corresponding window if opened */
                    BlogPostEditorFrame w = getProfile().findBlogEntryEditor(event);
                    if (w != null) {
                        w.afterPostSent();
                    }
                }
            }

            receive(new UpdateFrameEvent(this));
        }
    }

    private void onPublishEntry() {
        int[] rs = getTable().getSelectedRows();
        if (rs.length == 0) {
            return;
        }

        new OutboxThread(rs).execute();
    }

    protected abstract OutboxTableModel createTableModel();
}
